<!DOCTYPE html>
<html>
<head>
	<meta charset="utf-8" />
	<meta name="viewport" content="width=device-width, initial-scale=1">
	<title></title>
	<style type="text/css">
		*{
			margin: 0;
			padding: 0;
		}
		html{
			background: linear-gradient(#041231,#45416a);
		}
		canvas{
			display: block;
			/* animation: wave 1s infinite alternate; */
		}
		@keyframes wave{
			to{transform: rotate(20deg);}
		}
	</style>
</head>
<body>
<canvas id="myCanvas" width="600" height="600"></canvas>
<script type="text/javascript">
!function (e,w) {
	function Demo(option){
		Object.assign(this,{
			el: "#myCanvas",
			mainWidth: w.innerWidth,
			mainHeight: w.innerHeight,
			ms: 0,
		},option)
		this.init()
	}
	Demo.prototype = {
		init(){
			this.canvas = e.querySelector(this.el)
			this.cx = this.canvas.getContext("2d")
			this.canvas.setAttribute("width", w.innerWidth)
			this.canvas.setAttribute("height", w.innerHeight)
			this.addEvents()
			this.start()
		},
		addEvents(){
			w.addEventListener("resize",() => {
				this.mainWidth = w.innerWidth
				this.mainHeight = w.innerHeight
				this.canvas.setAttribute("width", w.innerWidth)
				this.canvas.setAttribute("height", w.innerHeight)
			})
		},
		draw(frame,isSingle){
			if(isSingle){
				frame()
			}else{
				this.timer = setInterval(() => {
					this.ms = performance.now()
					this.cx.save()
					this.cx.clearRect(0,0,this.mainWidth,this.mainHeight)
					frame()
					this.cx.restore()
				},1000/60)
			}
		},
		start(){
			const demo = this
			const treeList = []
			// 二叉树结构
			function Tree(option){
				Object.assign(this,{
					dep: 6, // 深度
					pos: [50,700],
					ang: -45, // 树轴朝向 deg | to right === 0
					open: 25, // 全局张角 deg
					len: 300, // 树根部位枝干长度 px
					lineWidth: 40, // 枝干粗细 px
					sway: true, // 是否开启摇摆
					swayRange: 5, // 摇摆幅度 deg
					phase: 0, // 摇摆相位 0 ~ 2*Math.PI
					swaySpeed: 1, // 摇摆速度
					color: [242,60,61,1], // 主题颜色 | hsla color
					endColor: "#fff", // 末端颜色 | string color
					endSize: 6, // 末端大小 px
					deDecay: 2, // 抗衰减系数 0 ~ infinity | 推荐 2
					showBone: false, // 是否显示骨骼 boolean
					boneColor: "#fff", // 骨骼颜色 | string color
					boneSize: 4, // 树根部位骨骼粗细 px
					showShadow: false, // 是否开启阴影 boolean
					shadowBlur: 5, // 阴影扩散范围 px
					shadowColor: "rgba(0,0,0,.5)", // 阴影颜色 | string color
					stillSolid: true, // 是否关闭末端半透明
					rdmLightness: true, // 是否开启随机明度
					colorAnimation: true, // 是否开启颜色变换动画
					cASpeed: 2, // 颜色变换速度 | 仅开启颜色动画有效
					cAOffset: true, // 是否开启颜色错位 | 仅开启颜色动画有效
					cAOffrange: .8, // 颜色错位幅度 | 仅开启颜色动画有效 | 推荐 0.8
				},option)
				this.init()
				// hsla(242, 60%, 61%, 1.0)
			}
			Tree.prototype = {
				// 初始化二叉树
				init(){
					let count = 0
					// 树节点结构
					function Node(dep,l,parent,pos){
						this.dep = dep
						this.l = l
						this.ch = null
						this.parent = parent || null
						this.pos = pos
						this.ang = 0
						this.delta = 0
						this.rdmSeed = Math.floor((Math.random()-.5) * 10)
						this.id = count++
					}
					Node.prototype = {
						// 计算双子节点属性
						calc(tree){
							this.ch && this.ch.forEach((c,i) => {
								let k = tree.deDecay / (c.dep + tree.deDecay) // 深度缩减系数 1 ~ ->0
								let len = tree.len * k // 本级枝干长度
								let thelta = i === 0 ? tree.open/2 : -tree.open/2 // 子节点分岔角
								this.delta = tree.sway ? Math.sin(demo.ms / 500 * tree.swaySpeed - c.dep / 2 + tree.phase) * tree.swayRange : 0 // 摇摆角动画
								c.ang = this.ang + this.delta + thelta // 子节点绝对角 == 基角 + 摇摆角 + 分岔角
								c.pos = demo.getP(this.pos,c.ang,c.l * len) // 子节点位置
								let ctrl = demo.getP(this.pos,c.ang - this.delta/2,c.l * len / 2)
								// demo.cx.strokeStyle = `rgba(${ 210 - k* 80 },140,255,${ (1-(1-k)/2).toFixed(2) - -.1 })` // 初次色彩设计
								let h = tree.color[0] - k* 80
								if(tree.colorAnimation){
									h = tree.color[0] - k* 80 + demo.ms / 20 * tree.cASpeed
									if(tree.cAOffset){
										h -= Math.floor(c.id * tree.cAOffrange)
									}
								}
								demo.cx.strokeStyle = `hsla(${ h },
								${ tree.color[1] }%,
								${ tree.rdmLightness ? tree.color[2] + c.rdmSeed : tree.color[2] }%,
								${ tree.stillSolid ? 1 : (1-(1-k)/2).toFixed(2) - -.1 })` // 自定义色彩
								demo.cx.lineWidth = tree.lineWidth * k
								demo.cx.lineCap = "round"
								// 绘制枝干
								demo.cx.moveTo(...this.pos)
								demo.cx.quadraticCurveTo(...ctrl,...c.pos)
								demo.cx.stroke()
								
								// 显示枝干骨骼
								if(tree.showBone){
									demo.cx.strokeStyle = tree.boneColor
									demo.cx.lineWidth = tree.boneSize * k
									demo.cx.beginPath()
									demo.cx.moveTo(...this.pos)
									demo.cx.lineTo(...c.pos)
									demo.cx.stroke()
								}
								// 开启投影
								if(tree.showShadow){
									demo.cx.shadowBlur = tree.shadowBlur
									demo.cx.shadowColor = tree.shadowColor
								}
								// 绘制末端
								if(c.dep === tree.dep){
									demo.cx.beginPath()
									demo.cx.arc(...c.pos,tree.endSize / 2,0,Math.PI * 2)
									demo.cx.closePath()
									demo.cx.fillStyle = tree.endColor
									demo.cx.fill()
								}
								
								
								// 绘制控制点
								// demo.cx.fillStyle = "#fff"
								// demo.cx.fillRect(ctrl[0] - 2,ctrl[1] - 2,4,4)
								
								demo.cx.beginPath()
							})
						}
					}
					let head = new Node(0,Math.random() * .4 + .8, null, this.pos)
					head.ang = this.ang
					let curr = [head]
					for(let i = 0; i < this.dep; i++){
						let temp = []
						curr.forEach(c => {
							let l = new Node(i + 1, Math.random() * 1 + .2, c, this.pos)
							let r = new Node(i + 1, Math.random() * .8 + .4, c, this.pos)
							c.ch = [l,r]
							temp.push(l,r)
						})
						curr = temp
					}
					this.head = head
				}
			}
			
			treeList.push(new Tree({
				dep: 4,
				len: 6000,
				lineWidth: 400,
				deDecay: .1,
				color: [242,30,61,1], // 主题颜色 | hsla color
			}))
			treeList.push(new Tree({
				color: [242,40,80,1], // 主题颜色 | hsla color
			}))
			treeList.push(new Tree({
				pos: [-100,700],
				ang: -20,
				dep: 4,
				phase: 1,
				color: [100,30,61,1], // 主题颜色 | hsla color
				cASpeed: .5
			}))
			treeList.push(new Tree({
				dep: 4,
				pos: [0,650],
				len: 100,
				lineWidth: 20,
				color: [100,60,61,1], // 主题颜色 | hsla color
			}))
			
			
			treeList.push(new Tree({
				dep: 3,
				pos: [800,750],
				len: 300,
				ang: -90,
				swaySpeed: 3,
				lineWidth: 10,
				phase: 2,
				swayRange: 30,
				colorAnimation: false
			}))
			treeList.push(new Tree({
				dep: 2,
				pos: [800,750],
				len: 300,
				ang: -70,
				swaySpeed: 3,
				lineWidth: 10,
				phase: 2,
				swayRange: 60,
				colorAnimation: false
			}))
			let renderTree = tree => {
				function calcAll (node) {
					node.calc(tree)
					node.ch && node.ch.forEach(c => calcAll(c))
				}
				calcAll(tree.head)
			}
			this.draw(() => {
				treeList.forEach(tree => {
					renderTree(tree)
				})
			})
		},
		// demo.getP()
		getP(p,r,l){
			return [p[0] + l * Math.cos(r * Math.PI / 180), p[1] + l * Math.sin(r * Math.PI / 180)]
		}
	}
	w.Demo = Demo
}(document,window)
var o = new Demo()
</script>
</body>
</html>